今天我們來探討跟 Array 相關的話題吧!當然不是每個語言都有內建 Array,而是有其他類似的,我們會一起來看看有什麼樣的不一樣囉!(今天比較像是自由探索,哈哈!)
package com.ryan.scala.oop
import scala.collection.mutable.ArrayBuffer
object Solution {
def main(args: Array[String]) {
val intArray1 = new Array[Int](5)
println(intArray1.mkString(","))
intArray1(0) = 100
println(intArray1.mkString(","))
val intArray2 = ArrayBuffer[Int]()
intArray2 += 1
intArray2 += (2, 3)
println(intArray2.mkString(","))
val intArray3 = ArrayBuffer[Int](4, 5, 6)
val intArray4 = intArray2 ++= intArray3
println(intArray4.mkString(","))
}
}
intArray1 雖然定義的時候是 val ,但是那是說 intArray1 只能一直是這個 Array,但是 Array 的內容是可以修改的 ( Array[Int],表示這是一個 Int 的 Array,這個部分就是 Scala 的 Generics,以後會再提到),例如這個例子我們把第一個元素改成 100 ( intArray1(0) = 100 ),而此 Array 的第一個元素真的是可以被改成 100 。Scala 的 Array 當定義之後,Size 就不能再修改了。ArrayBuffer (要 import scala.collection.mutable.ArrayBuffer),像是 intArray2 ,這裡不用 new 是因為我們先前講過的 Companion Object 中的 apply Method。而 intArray2 可以看到我們會透過 += 來把元素加到裡頭。越先加進來的 index 越小。假使今天有兩個 ArrayBuffer 要接在一起,例如 intArray2 跟 intArray3 要接在一起就要使用 ++= 。因為 ArrayBuffer 可以改變長度,所以也提供了像是 remove , insert 等等 Method,而與 Array 之間也可以透過 toArray , toBuffer 互相轉換囉!而不管是 Array 還是 ArrayBuffer 都有提供像是 sum , max , min 等等方法可以讓你在一些情境下更方便使用囉!最後我們來看當我們想把全部元素印出來,彼此之間用逗號隔開時,我們用了 mkString(","),這會 iterate 全部的元素並且以逗號來連接各元素後,回傳 String 囉!更多可以參考這裡和這裡。list1 = ['a', 'b', 1, 2]
print(list1[1:3])
list1.append('c')
print(list1)
list1.remove('b')
print(list1)
list2 = list1
list2[0] = 'd'
print(list1)
print(list2)
list1。因為可變,所以不管是添加 ( list1.append('c'))或刪除元素 ( list1.remove('b'))也都可以,而每次增加元素的時候,底層的 Array 就會改變大小,當然 Python 本身有做一些優化來避免不斷去變更長度。而在用 index 取值的時候,因為底層是 Array,所以效能跟一般的 Array 類似,也就是跟你 index 值的大小無關。假使我們要取 list 中的某一段可以以 <list>[<start>:<stop>] 的方式,例如 list1[1:3] 會取得以 list1[1] , list1[2] 組成的 list,也就是 [‘b’, 1]。要注意的是,假使我們把 list1 賦予給 list2 ,那麼修改 list2[0] , list1[0] 也會跟著看到改變後的結果囉!可以思考看看為什麼會這樣。(提示:剛才說 list 裡頭存的是 Pointer)['b', 1]
['a', 'b', 1, 2, 'c']
['a', 1, 2, 'c']
['d', 1, 2, 'c']
['d', 1, 2, 'c']
package main
import "fmt"
func main() {
var array1 [5]string
array1[0] = "a"
array1[1] = "b"
array1[2] = "c"
array1[3] = "d"
array1[4] = "e"
slice1 := make([]string, 5, 5)
slice1[0] = "a"
slice1[1] = "b"
slice1[2] = "c"
fmt.Println(len(slice1))
fmt.Println(cap(slice1))
slice2 := array1[2:4]
slice2[0] = "C"
fmt.Println(len(slice2))
fmt.Println(cap(slice2))
fmt.Println(array1)
slice3 := make([]string, len(slice2))
copy(slice3, slice2)
slice3[0] = "A"
fmt.Println(slice2)
fmt.Println(slice3)
ptr),也就是真正存放資料的 Array,由此可以知道 Slice 其實是一個 Array 的 “某一段”,第二個是 len,也就是這個 “某一段” 裡頭有多少元素,第三個是 capacity,也就是從 ptr 指到的地方開始,一直到最後一個元素到底共有幾個。由於 Slice 存放的是 Pointer,會不會有別的 Slice 也指向同一個 Array 呢?答案是會的喲!所以可能會造成某個 Slice 修改了某值,而另一個 Slice 也看到了修改後的結果,造成非預期的影響。如果要解決這個問題,那麼我們就要使用內建的 copy ,讓不同 Slice 都有自己指向的獨一無二 Array,就能避免這個問題。var array1 [5]string 宣告一個長度是 5 的 Array array1 ,並且將每個元素都賦值。而 slice1 是透過 make 來創造 ( make用來創造 slice, map, 以及 channel。後兩者是什麼以後會提到囉),而 make([]string,5,5)表示創造一個 Slice of string 並且 len 跟 capacity 都是 5。再來我們一樣給 slice1賦值,此時透過 len 和 cap 分別得到 slice1 的 len 是 5,而 cap也是 5。為什麼 len 是 5 不是 3 呢?因為這時候其實沒被特別賦值的部分在被宣告的時候也有初始值唷!這時我們讓 slice2 是 array1[2:4] ,也就是把 slice2 的 ptr 指到 array1[2] 之處,這時可以看到 slice2 的 len 是 2 (因為 array1[2:4]有 3 個元素),cap 是 3 (因為從 array1[2] 開始到最尾端還有 3 個元素)。這時我們試著改變 slice2[0] = "C" ,也就是 array1[2] 的位置,結果發現印出 array1 的時候,確實值也被改變囉!(就是因為 slice 只是用一個 Pointer 指到 array)。如果要避免這個狀況就要像 slice3 用 copy 來創造出另一個新的空間,這樣一來, slice3 跟 slice2 就互不干擾了!5
5
2
3
[a b C d e]
[C d]
[A d]
fn main() {
let mut array1 = [1, 2, 3, 4, 5];
let mut array2 = array1;
array2[0] = 6;
for x in array1.iter() {
println!("{}", x);
}
let mut vec1 = Vec::new();
vec1.push(1);
let mut vec2 = vec![1, 2, 3];
match vec2.pop() {
Some(x) => println!("{}", x),
None => println!("None"),
}
println!("{}", vec2.pop().unwrap());
vec2.pop().unwrap();
// vec2.pop().unwrap();
let slice1 = &mut[1, 2, 3, 4, 5];
slice1[0] = 6;
}
let mut 來進行變數綁定唷!Array 本身不能直接被 iterate,必須像我們之前所說,要像是 array1.iter() 變成一個迭代器才可以。那麼今天如果我們需要一個比較彈性,可以調整長度,但又像是 Array,我們就會選用 Vector。Vector 因為比較靈活,所以也滿常被使用的。建立 Vector 的方式有兩種,第一種是我們透過 Vec::new() ,先建立出一個空的 Vector 後,再把值透過 push 放入 Vector。第二種是透過 vec! 這個 Macro 來建立。至於使用上跟 Array 差不多,都是可以修改資料,只要一開始用 mut 做綁定。我來看一個比較有趣的是 vec2.pop() ,這個 Function 會從尾端把元素給取出來 (Vector 的長度就會減一),那是如果一直 pop() ,最後沒值會怎樣呢?答案是不會怎樣,但是我要怎麼知道已經沒值了?答案是 vec2.pop() 會回傳的是一個 Option 的 enum type,沒錯,我們之前有講過。所以有值的話,就會被包在 Some ,不然就會回 None 囉!另外來講下,因為每次如果遇到像 Option這種,用 match 的邏輯都差不多的話,那麼每次都寫成像上述的 Code 就太累贅,所以 Rust 提供另一個 Function 叫 unwrap ,像是 vec2.pop().unwrap() ,如果有值就會直接取出來給你,會自動判斷是不是 Some 。但是如果是 None 的話就會直接送你一個 Panic 啦!如果你覺得直接送 Panic 太殘忍,想要改給一個 default 值的話,可以用 unwrap_or(<default value>) 囉!有興趣可以參考這裡。slice1 = &mut[1,2,3,4,5] ,是說 slice1 是一個指向 [1,2,3,4,5] 這個 Array 的 reference (透過 &),而 mut 表示可以修改的權限。然後我們就可以像修改 Array 一樣來修改 Slice 囉!今天把四個語言跟 Array 相關的資料結構大致上掃過一遍,至於每個語言在不同資料結構詳細的用法,大家有興趣我相信都可以在網路上找到很多資訊,主要就是讓大家一次可以去看看不同語言的不同與相同之處。那麼我們就明天見囉!